In [1]:
#sourse link = https://data.gov.ua/dataset/1b428b3e-d613-43a2-9781-94c26959db06 ,2020
# Таблиця не у машиночитанному форматі з переліком номерів та назв автобусних...

import altair as alt
import pandas as pd
import numpy as np

import geopandas as gpd
alt.data_transformers.disable_max_rows()


df = pd.read_csv('uc.csv')
df.head()


df2 = pd.read_csv('uss.csv')
df2.head()

ucor = gpd.read_file('ukraine_2.json')


# Для візуалізацій я використовувала не повний маршрут, а лише точки відправлення та прибуття. 
# Це не дуже добре сказалось на детальності даних, багато малих міст (проміжних зупинок) проігноровано. 
In [2]:
# Отримаємо відстань, час прибуття та відправлення 
k = 1
for i in range(3, 31752):
    row = df2.loc[i]
    if pd.isnull(row['Назва автобусного маршруту ']):
        pass
    else:
        df2['відправлення '][i] = df2['відправлення '][i+1]  
        df2['прибуття '][k] = df2['прибуття '][i-1] 
        df2['відстань'][k] = df2['відстань'][i-1]
        k = i
#print(df2)
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/ipykernel_launcher.py:8: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/ipykernel_launcher.py:9: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  if __name__ == '__main__':
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/ipykernel_launcher.py:10: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  # Remove the CWD from sys.path while we load stuff.
In [3]:
# Відсікаємо всі села. Цей спосіб не є дуже добрий, але він був потрібен для того щоб уникнути дублікати міст. 
# Можна було не видаляти всі села, лише в разі дублікатів лишати те, в якого вона найбільша.
# Але дані про поуляцію не є повтоцінними. 
df = df[df['type']!= 'village']
df.reset_index(drop=True, inplace=True)

print(df)
          koatuu               town           type                        obl  \
0      110100000     м. Сімферополь           city  Автономна Республіка Крим   
1      110165300  смт Аерофлотський            smt  Автономна Республіка Крим   
2      110165600    смт Гресівський            smt  Автономна Республіка Крим   
3      110165601         с. Бітумне  small_village  Автономна Республіка Крим   
4      110165800  смт Комсомольське            smt  Автономна Республіка Крим   
...          ...                ...            ...                        ...   
2507  8000000000            м. Київ           city                       Київ   
2508  8500000000     м. Севастополь           city                Севастополь   
2509  8536310300        м. Інкерман           town                Севастополь   
2510  8536965300           смт Кача            smt                Севастополь   
2511  8536990203        с. Сонячний  small_village                Севастополь   

                dis oldname        lat        lon   pop_2014   pop_2015  \
0       Сімферополь     NaN  44.952139  34.102457   338319.0        NaN   
1       Сімферополь     NaN  45.019551  34.001051     2359.0        NaN   
2       Сімферополь     NaN  45.010178  34.026487    11509.0        NaN   
3       Сімферополь     NaN  45.019471  34.045061        NaN        NaN   
4       Сімферополь     NaN  45.019896  34.021467     4892.0        NaN   
...             ...     ...        ...        ...        ...        ...   
2507           Київ     NaN  50.450107  30.524050  2868702.0  2887974.0   
2508    Севастополь     NaN  44.605443  33.522084   344853.0        NaN   
2509  Балаклавський     NaN  44.618104  33.604327    12028.0        NaN   
2510   Нахімовський     NaN  44.775508  33.543754     5137.0        NaN   
2511   Нахімовський     NaN  44.786110  33.619190        NaN        NaN   

       pop_2016  pop_latest  
0           NaN    338319.0  
1           NaN      2359.0  
2           NaN     11509.0  
3           NaN       221.0  
4           NaN      4892.0  
...         ...         ...  
2507  2906569.0   2906569.0  
2508        NaN    344853.0  
2509        NaN     12028.0  
2510        NaN      5137.0  
2511        NaN      1778.0  

[2512 rows x 12 columns]
In [4]:
# Готуємо координати міст до роботи. 
cor = {}
cor['T'] = df['town'].apply(lambda x: ' '.join(x.split(' ')[1:]))
cor['lat'] = df['lat']  
cor['lon'] = df['lon']  
cor['obl'] = df['obl']  
cor = pd.DataFrame.from_dict(cor)

cor.drop_duplicates('T', keep='first',  inplace=True)
cor.reset_index(drop=True, inplace=True)


print(cor)
                  T        lat        lon                        obl
0       Сімферополь  44.952139  34.102457  Автономна Республіка Крим
1     Аерофлотський  45.019551  34.001051  Автономна Республіка Крим
2       Гресівський  45.010178  34.026487  Автономна Республіка Крим
3           Бітумне  45.019471  34.045061  Автономна Республіка Крим
4     Комсомольське  45.019896  34.021467  Автономна Республіка Крим
...             ...        ...        ...                        ...
2040           Київ  50.450107  30.524050                       Київ
2041    Севастополь  44.605443  33.522084                Севастополь
2042       Інкерман  44.618104  33.604327                Севастополь
2043           Кача  44.775508  33.543754                Севастополь
2044       Сонячний  44.786110  33.619190                Севастополь

[2045 rows x 4 columns]
In [5]:
# Лишаємо лише дані про початкову та кінцеві станціі. Приводимо їх до ладу.  
df2_test = df2[df2['Назва автобусного маршруту '].isnull()==False]

df2_m = {}
df2_m['from'] = df2_test['Назва автобусного маршруту '].apply(lambda x: x.split('-')[0].strip())
df2_m['to'] = df2_test['Назва автобусного маршруту '].apply(lambda x: x.split('-')[1].strip())
df2_m['start_t'] =  df2_test['відправлення ']
df2_m['end_t'] =  df2_test['прибуття ']
df2_m['l'] =  df2_test['відстань']

df2_m = pd.DataFrame.from_dict(df2_m)

df2_m.reset_index(drop=True, inplace=True)

print(df2_m)
                       from            to        start_t      end_t         l
0                   Черкаси          Київ  відправлення   прибуття   відстань
1                Дебальцеве   Красний Луч           8:45      10:00        52
2                 Городенка      Чернівці           7:35       9:15        64
3                       Бар     Віньківці           6:10       7:20        38
4                 Скадовськ          Київ           6:45      21:30       707
...                     ...           ...            ...        ...       ...
3905  Кам'янець_Подільський        Затока          14:30       7:00       747
3906     Хорошів (Іршанськ)          Київ           5:00       8:20       205
3907     Хорошів (Іршанськ)          Київ          13:30      17:00       205
3908                Могилів   Подільський           9:20      17:00       386
3909                Олевськ  Хмельницький           3:00  прибуття   відстань

[3910 rows x 5 columns]
In [6]:
# Так як дані не ідеальні, то деякі з них при конвертаціі ламались, тому робимо перевірки на відність.
#df2_m['from_lat'] = df2_m['from']
fr_lat = []
fr_lon = []
to_lat = []
to_lon = []

ex =[]
for i in range (0, 3910):
    row = df2_m.loc[i]
    fr = row['from']
    to = row['to']
    
    if (cor[cor['T'] == fr]['lat']).empty:
        fr_lat.append(0.0)
    else:
        fr_lat.append(float(cor[cor['T'] == fr]['lat']))
    
    if (cor[cor['T'] == fr]['lon']).empty:
        fr_lon.append(0.0)
    else:
        fr_lon.append(float(cor[cor['T'] == fr]['lon']))
        
    if (cor[cor['T'] == to]['lat']).empty:
        to_lat.append(0.0)
    else:
        to_lat.append(float(cor[cor['T'] == to]['lat']))
        
    if (cor[cor['T'] == to]['lon']).empty:
        to_lon.append(0.0)
    else:
        to_lon.append(float(cor[cor['T'] == to]['lon']))
    
    
df2_m['from_lat'] = fr_lat
df2_m['from_lon'] = fr_lon
df2_m['to_lat'] = to_lat
df2_m['to_lon'] = to_lon   

#print(df2_m)
In [10]:
test = df2_m[df2_m['from_lat']!=0]
test = test[test['from_lon']!=0]


test = test[test['to_lat']!=0]
test = test[test['to_lon']!=0]


points= alt.Chart(test).mark_point(filled=True, color="black", size = 9).encode(
    longitude='to_lon:Q',
    latitude='to_lat:Q', 
    size = alt.Size('count(to)+ count(from):Q', title = 'Кількість відправлень:'),
    tooltip = alt.Tooltip('to')
).properties(
    title='', 
    width=600,
    height=600)


flows = alt.Chart(test).mark_rule(strokeCap = 'round', color ='orange', opacity= 0.3).encode(
    longitude = alt.Longitude('from_lon:Q'),
    latitude = alt.Latitude('from_lat:Q'),
    longitude2 = alt.Longitude2('to_lon:Q'),
    latitude2 = alt.Latitude2('to_lat:Q'), 
    tooltip = alt.Tooltip(['from', 'to'])
).properties(
    title='Щоденні автобусні відправлення-прибуття по Україні, 2020 р.', 
    width= 500,
    height=300
)

alt.layer( flows + points).properties(width = 500, height = 300).configure_legend(orient = 'bottom-left').configure_view(strokeWidth = 0)
Out[10]:
In [ ]:
# Ця візуалізація показує які з якого міста та в яке є маршрути. 
# Помаранчевими лініями я хотіла показати наскільки багато маршрутів відбувається на щодень на Україні.
# Поінти дозволяють зрозуміти лідерів по кількості відправлень + приїздів та співставити це з реальними містами. 
# Ця візуалізація для загального розуміння картини, детальні дані з неї дізнатись важко.
In [30]:
test['fr']= test['from']

input_ratio1 = alt.binding_select(options = sorted(test.to.unique()))
input_ratio11 = alt.binding_select(options = sorted(test.fr.unique()))


select_city_to = alt.selection_single(name="FROM", fields = ['to'], bind = input_ratio1, empty = 'all',  init = {'to': 'Дніпро'})
select_city_from = alt.selection_single(name="TO", fields = ['fr'], bind = input_ratio11, empty = 'all', init = {'fr': 'Дніпро'})



ukraine = alt.Chart(ucor).project().mark_geoshape(color ='#b3cffc').encode(
    tooltip = alt.Tooltip('NAME_1:N'), 
    #opacity = alt.condition
    detail = alt.Detail('NAME_1:N')
)


flow_f_to = alt.Chart(test).mark_rule(strokeCap = 'round', color ='orange', opacity= 0.9).encode(
    longitude = alt.Longitude('from_lon:Q'),
    latitude = alt.Latitude('from_lat:Q'),
    longitude2 = alt.Longitude2('to_lon:Q'),
    latitude2 = alt.Latitude2('to_lat:Q'), tooltip = alt.Tooltip('from:N')
).transform_filter(select_city_to).properties(
    title='Щоденні автобусні прибуття по Україні, 2020 р.', 
    width=600,
    height=600)


home_to = alt.Chart(test).mark_text().encode(
longitude='to_lon:Q',
    latitude='to_lat:Q', text = alt.Text('to:N') ).add_selection(select_city_to).transform_filter(select_city_to)



flow_f_from = alt.Chart(test).mark_rule(strokeCap = 'round', color ='orange', opacity= 0.9).encode(
    longitude = alt.Longitude('from_lon:Q'),
    latitude = alt.Latitude('from_lat:Q'),
    longitude2 = alt.Longitude2('to_lon:Q'),
    latitude2 = alt.Latitude2('to_lat:Q'), tooltip = alt.Tooltip('to:N')
).transform_filter(select_city_from).properties(
    title='Щоденні автобусні відправлення по Україні, 2020 р.', 
    width=600,
    height=600)


    
points2_from = alt.Chart(test).mark_point(filled=True, color="black", size = 9).encode(
    longitude='to_lon:Q',
    latitude='to_lat:Q', 
    size = alt.Size('count(to):Q', title= 'Кількість рейсів'),
    tooltip = alt.Tooltip('to')
).add_selection(select_city_from
).transform_filter(select_city_from
).properties(
    title='', 
    width=600,
    height=600
)

points_to = alt.Chart(test).mark_point(filled=True, color="black", size = 9).encode(
    longitude='from_lon:Q',
    latitude='from_lat:Q', 
    size = alt.Size('count(from):Q',title= 'Кількість рейсів'),
    tooltip = alt.Tooltip('from')
).transform_filter(select_city_to
).properties(
    title='', 
    width=600,
    height=600
)

home_from = alt.Chart(test).mark_text().encode(
longitude='from_lon:Q',
    latitude='from_lat:Q', text = alt.Text('from:N') ).transform_filter(select_city_from)

pl1 = ukraine + flow_f_to   + points_to + home_to
pl2 = ukraine + flow_f_from + points2_from + home_from

alt.hconcat(pl1.properties(width = 450, height = 300), pl2.properties(width = 100, height = 300)).configure_concat(spacing = 0)
Out[30]:
In [ ]:
# Цією візуалізацією я хотіла показати міста відправлення - прибуття так, щоб їх можна було зрозуміти. 
# Людина може вибрати прибуття місто та подивитись те, куди з нього ідять прямі рейси. Та навпаки з відправленням. 
# Мінусом цієї візуалізації є 2 дуже великий списки міст. 

# З візуалізаціі можна зрозуміти, що автобуси, які виїзжають з міста А в місто Б не ідять у зворотньому напрямку. 
# (Так як графіки прибуття - відправлення дуже відрізняються між собою)
# Також можна зробити висновок, що автобусні перевезення здебільшого двох типів: 
# 1 переїзд у межах області та її сусідів по периметру 
# 2 переїзд з одного боку країну у інший
In [11]:
df2_m = df2_m[df2_m['end_t']!='прибуття ']
df2_m = df2_m[df2_m['l']!='Бобринець АС']
df2_m = df2_m[df2_m['start_t'].notna()]
df2_m = df2_m[df2_m['end_t'].notna()]

df2_m['l'] =  df2_m['l'].apply(lambda x: int(x))
df2_m['start_t'] =  df2_m['start_t'].apply(lambda x: int(x.split(':')[0]))
df2_m['end_t'] =  df2_m['end_t'].apply(lambda x: int(x.split(':')[0]))
In [29]:
ch1= alt.Chart(df2_m).mark_rect().encode(
    x = alt.X('start_t:N', title='Час відправлення'),
    y = alt.Y('mean(l):Q', title= 'кількість відправлень'),
    #color = alt.Color('l:Q', scale = alt.Scale(scheme = 'pinkyellowgreen'))
).properties(width = 400,title = 'Залежність відставні маршруту від часу відправлення')

ch2=alt.Chart(df2_m).mark_rect().encode(
    x = alt.X('end_t:N', title='Час прибуття'),
    y = alt.Y('mean(l):Q', title = 'кількість відправлень'),
    #color = alt.Color('l:Q', scale = alt.Scale(scheme = 'pinkyellowgreen'))
).properties(width = 400,title = 'Залежність відставні маршруту від часу прибуття')

ch3 = alt.Chart(df2_m).mark_rect().encode(
    x = alt.X('start_t:N', title='Час відправлення'),
    y = alt.Y('count(start_t):Q', title= 'кількість відправлень'),
    #color = alt.Color('l:Q', scale = alt.Scale(scheme = 'pinkyellowgreen'))
).properties(width = 400,title = 'Розподіл часу відправлення')

ch4 = alt.Chart(df2_m).mark_rect().encode(
    x = alt.X('end_t:N', title='Час прибуття'),
    y = alt.Y('count(end_t):Q', title = 'кількість відправлень'),
    #color = alt.Color('l:Q', scale = alt.Scale(scheme = 'pinkyellowgreen'))
).properties(width = 400,title = 'Розподіл часу прибуття')



alt.vconcat(alt.hconcat(ch1,ch2), alt.hconcat (ch3,ch4))
Out[29]:
In [ ]:
# Розглянемо графіки залежності часу відправлення/прибуття та довжини поїздки 
# В мене була гіпотеза, що найдовші рейси відбуваються або до початку або після робочого дня
# А приходять зранку наступного дня, або пізно ввечері. 
# Як ми бачимо, це підтвержується.

# Найбільш частими є відправлення 5-8 ранку (до робочого дня) також є пік на 13-15 година, після обіду. 
# Прибуття здебільшого посеред дня, рідше вночі. 

# Я хотіла зобразити розподіл, barchart добре підходять

# Хотіла зробити графік зі слайдером (час поїздки) та на основі його будувати графіки частоти, але він може 
# показувати дані лише для конкретної відстані, не для інтервалу (100-200 км). Тому він не є коректним.
In [42]:
test = df2_m[df2_m['from_lat']!=0]
test = test[test['from_lon']!=0]


test = test[test['to_lat']!=0]
test = test[test['to_lon']!=0]

time_slider = alt.binding_range(min=0, max=24, step=1)
time_selection = alt.selection_single(bind=time_slider, fields=['start_t'], name="Start_time")


points= alt.Chart(test).mark_point(filled=True, color="black", size = 9).encode(
    longitude='to_lon:Q',
    latitude='to_lat:Q', 
    size = alt.Size('count(to)+ count(from):Q', title = 'Кількість відправлень:'),
    tooltip = alt.Tooltip('to')
).add_selection(time_selection).transform_filter(time_selection).properties(
    title='', 
    width=600,
    height=600)


flows = alt.Chart(test).mark_rule(strokeCap = 'round', color ='orange', opacity= 0.3).encode(
    longitude = alt.Longitude('from_lon:Q'),
    latitude = alt.Latitude('from_lat:Q'),
    longitude2 = alt.Longitude2('to_lon:Q'),
    latitude2 = alt.Latitude2('to_lat:Q'), 
    tooltip = alt.Tooltip(['from', 'to'])
).transform_filter(time_selection).properties(
    title='Щоденні автобусні відправлення по Україні, 2020 р.', 
    width= 500,
    height=300
)

alt.layer( ukraine+flows + points).properties(width = 500, height = 300).configure_legend().configure_view(strokeWidth = 0)
Out[42]:
In [ ]:
#На цьому графіку ми бачимо залежність довжини перевезень від часу. 
# 5 ранку - 17 - невеличкі перевезення між областями. 
# Після 16 - перевезення між маленькими містами поступово зникають і лишаються лише довготривалі перевезення.